package com.ctbri.vSearch.frame;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

import org.apache.log4j.Logger;
import org.elasticsearch.client.transport.TransportClient;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;

import com.ctbri.vSearch.util.PropUtil;

@Repository("neo4jPooledConnection")
@Scope("singleton")
public class Neo4jPooledConnection {

	/**
	 * 连接数据元实体
	 */
	private Neo4jDataBean neo4jDataBean;
	/**
	 * 记录创建的总的连接数
	 */
	private AtomicInteger contActive;
	/**
	 * 空闲连接队列
	 */
	public ArrayBlockingQueue<Connection> maxConnection ;
	/**
	 * 活动连接队列
	 */
	private ConcurrentHashMap<Connection,Integer> activeConnection ;
	/**
	 * 将线程和连接绑定，保证事务能统一执行
	 */
	private ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();
	private final int TRYTIMES = 5;
	Lock releaseLock = new ReentrantLock();
	private Logger log = Logger.getLogger(getClass());
	
	private Neo4jPooledConnection(){
	}
	
	/**
	 * 
	 */
	@PostConstruct
	public void startPool(){
		this.neo4jDataBean = loadNeo4jDataBean();//初始化连接池配置信息
		this.maxConnection = new ArrayBlockingQueue<>(neo4jDataBean.getMaxConnections());
		this.activeConnection = new ConcurrentHashMap<Connection,Integer>(neo4jDataBean.getMaxActiveConnections());
		this.threadLocal = new ThreadLocal<Connection>();
		this.contActive = new AtomicInteger(0);
		try {
			Class.forName("org.neo4j.jdbc.Driver");
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		initConnectionPool();
	}
	
	/**
	 * 销毁线程池
	 */
//	@PreDestroy
	public void shutdownPool(){
		System.out.println("关闭Neo4j连接池！");
		Set<Connection> keys = activeConnection.keySet();
		Iterator<Connection> ikeys =keys.iterator();
		while(ikeys.hasNext()){
			Connection tc = ikeys.next();
			activeConnection.remove(tc);
			try {
				tc.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
		while(!maxConnection.isEmpty()){
			Connection tc = maxConnection.poll();
			try {
				tc.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}
	
	/**
	 * 初始化连接池
	 */
	private void initConnectionPool() {
		if(this.neo4jDataBean == null){
			this.neo4jDataBean = loadNeo4jDataBean();
		}
		AtomicInteger errNum =new AtomicInteger(TRYTIMES);
    	while(contActive.get()< neo4jDataBean.getInitConnections()) {  
    		Connection conn = newConnection();
            // 初始化最小连接数  
    		if(conn==null){
    			if(errNum.decrementAndGet()<0){
    				log.info("初始化连接为容忍失败次数："+errNum.get());
    				break;
    			}
    		}
            try {
				maxConnection.put(conn);
			} catch (InterruptedException e) {
				e.printStackTrace();
				log.error("添加连接失败！",e);
			}  
            contActive.incrementAndGet();  
            log.info("初始化neo4j连接："+conn.hashCode());
        }
    	log.info("初始化Neo4j连接数为："+ contActive.get());
	}
	/**
	 * 创建一个新的链接
	 * @return
	 */
	private Connection newConnection(){
		Connection conn = null;
		try {
			conn = DriverManager.getConnection(
					neo4jDataBean.getUrl()+":"+neo4jDataBean.getPort()+"/", 
					neo4jDataBean.getUsername(), 
					neo4jDataBean.getPassword());
		} catch (SQLException e) {
			e.printStackTrace();
			log.error("初始化连接异常",e);
		}
		System.out.println("初始化连接成功："+conn.hashCode());
		return conn;
	}
	/**
     * 获取当前连接
     * @return
     */
    public Connection getCurrentConnecton(){  
    	Connection conn = threadLocal.get();  
        if(conn == null){  
            conn = getConnection();  
        }  
        return conn;  
    }
    /**
     * 获得连接
     * 1. 判断是否有空闲连接如果有返回
     * 2. 如果没有，查看是否可以创建新连接放入maxConnection中,如果可以创建
     * 3. 如果不可以 ,阻塞等待
     * @return
     */
	private Connection getConnection() {  
		Lock putactivelock = new ReentrantLock();
    	Connection conn = null;
    	AtomicInteger errTimes = new AtomicInteger(TRYTIMES);
        try {  
            while(conn==null && errTimes.get()>0){
				if (maxConnection.size() > 0) {//如果空闲线程为空
		             conn = maxConnection.poll();  
		             if (conn != null) {  
		                 threadLocal.set(conn);
		                 activeConnection.put(conn,0);//添加当前活动链接元素
		             }else {
		            	 errTimes.decrementAndGet();
		            	 System.out.println(Thread.currentThread().getName()+"没有空闲线程"+errTimes.get());
		             }
		         } else {  
		        	 //如果活动线程数小于活动队列可存放空间
		             conn = newConnection();  //可能为空
		             if(conn == null ){
		            	 errTimes.decrementAndGet();
		             }else{
		            	 putactivelock.lock();
		            	 threadLocal.set(conn);
		            	 activeConnection.put(conn,0);//添加当前活动链接
		            	 putactivelock.unlock();
		             }
	            } 
			}
        } catch (Exception e) {  
            log.error("获取连接失败!",e);
            conn = null;
        } 
        return conn;  
    }  
	/***
	 * 释放链接
	 * 1. 如果不存在则返回false，不做操作
	 * 2. 如果存则：原子操作
	 * 		从threadlocal中移除
	 * 		从当前活动链接中移除
	 * 		把链接放入连接池中
	 */
	public boolean releaseConnection(Connection conn) {
	
			if(!activeConnection.containsKey(conn)){
				return false;
			}
			releaseLock.lock();
			try {
				threadLocal.remove();
				activeConnection.remove(conn);
				maxConnection.put(conn);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}finally{
				releaseLock.unlock();
			}
		return true;
	}
	/***
	 * 销毁链接
	 * 1. 如果链接不存在连接池中则返回false，不做操作
	 * 2. 如果存在则从活动列表中移除，并关掉。
	 */
	public boolean destroyConnection(Connection conn) {
		if(conn == null){
			return true;
		}
		if(!activeConnection.containsKey(conn)){
			return false;
		}
		releaseLock.lock();
		try {
			threadLocal.remove();
			activeConnection.remove(conn);
			conn.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}finally{
			releaseLock.unlock();
		}
		return true;
	}
	/***
	 * testInfo: 可以正确读取配置文件信息到Neo4jDataBean
	 * @return
	 */
	private Neo4jDataBean loadNeo4jDataBean(){
		Neo4jDataBean neo4jBean = new Neo4jDataBean();
		neo4jBean.url = PropUtil.getValue("neo4j.url");
		neo4jBean.port = PropUtil.getValue("neo4j.port");
		neo4jBean.username = PropUtil.getValue("neo4j.username");
		neo4jBean.password = PropUtil.getValue("neo4j.password");
		neo4jBean.minConnections =Integer.valueOf( PropUtil.getValue("neo4j.minConnections"));
		neo4jBean.maxConnections = Integer.valueOf(PropUtil.getValue("neo4j.maxConnections"));
		neo4jBean.initConnections = Integer.valueOf(PropUtil.getValue("neo4j.initConnections"));
		neo4jBean.maxActiveConnections = Integer.valueOf(PropUtil.getValue("neo4j.maxActiveConnections"));
		neo4jBean.connectionTimeOut = Integer.valueOf(PropUtil.getValue("neo4j.connectionTimeOut"));
		return neo4jBean;
	}

	private	class Neo4jDataBean{
		/**
		 * neo4j地址
		 */
		private String url;
		/**
		 * neo4j端口号
		 */
		private String port;
		/**
		 * 用户名
		 */
		private String username;
		/**
		 * 密码
		 */
		private String password;
		/**
		 * 最小连接数
		 */
		private int minConnections;
		/**
		 * 最大连接数
		 */
		private int maxConnections;
		/**
		 * 初始化连接数
		 */
		private int initConnections;
		/**
		 * 重复获得连接的频率
		 */
		/**
		 * 最大允许的连接数，和数据库对应
		 */
		private int maxActiveConnections;
		/**
		 * 连接超时时间
		 */
		private long connectionTimeOut;
		public String getUrl() {
			return url;
		}
		public void setUrl(String url) {
			this.url = url;
		}
		public String getPort() {
			return port;
		}
		public void setPort(String port) {
			this.port = port;
		}
		public String getUsername() {
			return username;
		}
		public void setUsername(String username) {
			this.username = username;
		}
		public String getPassword() {
			return password;
		}
		public void setPassword(String password) {
			this.password = password;
		}
		public int getMinConnections() {
			return minConnections;
		}
		public void setMinConnections(int minConnections) {
			this.minConnections = minConnections;
		}
		public int getMaxConnections() {
			return maxConnections;
		}
		public void setMaxConnections(int maxConnections) {
			this.maxConnections = maxConnections;
		}
		public int getInitConnections() {
			return initConnections;
		}
		public void setInitConnections(int initConnections) {
			this.initConnections = initConnections;
		}
		public int getMaxActiveConnections() {
			return maxActiveConnections;
		}
		public void setMaxActiveConnections(int maxActiveConnections) {
			this.maxActiveConnections = maxActiveConnections;
		}
		public long getConnectionTimeOut() {
			return connectionTimeOut;
		}
		public void setConnectionTimeOut(long connectionTimeOut) {
			this.connectionTimeOut = connectionTimeOut;
		}
		@Override
		public String toString() {
			return "Neo4jDataBean [url=" + url + ", port=" + port + ", username=" + username + ", password=" + password
					+ ", minConnections=" + minConnections + ", maxConnections=" + maxConnections + ", initConnections="
					+ initConnections + ", maxActiveConnections=" + maxActiveConnections + ", connectionTimeOut="
					+ connectionTimeOut + "]";
		}
	}
	
}
